//
// This code was created by Jeff Molofee '99 (ported to Linux/GLUT by Richard Campbell '99)
//
// If you've found this code useful, please let me know.
//
// Visit me at www.demonews.com/hosted/nehe 
// (email Richard Campbell at ulmont@bellsouth.net)
//
#include <GL/glut.h>    // Header File For The GLUT Library 
#include <GL/gl.h>	// Header File For The OpenGL32 Library
#include <GL/glu.h>	// Header File For The GLu32 Library
#include <unistd.h>     // Header file for sleeping.
#include <stdio.h>

/* ascii code for the escape key */
#define ESCAPE 27

int twinkle;

/* T pressed (1 = yes, 0 = no) */
int tp;

#define num 50

/* The number of our GLUT window */
int window; 


typedef struct
{
    int r, g, b;
    GLfloat dist;
    GLfloat angle;
}stars;

stars star[num];


GLfloat zoom = -15.0f;
GLfloat tilt = 90.0;
GLfloat spin;
GLuint loop;
GLuint texture[1];

struct Image {
    unsigned long sizeX;
    unsigned long sizeY;
    char* data;
};
typedef struct Image Image;

int ImageLoad(char* filename, Image* image)
{
    FILE* file;
    unsigned long size;
    unsigned long i;
    unsigned short int planes;
    unsigned short int bpp;
    char temp;

    if ((file = fopen(filename, "r")) == NULL)
    {
        printf("File Not Found: %s\n", filename);
        return 0;
    }

    fseek(file, 18, SEEK_CUR);

    if((i = fread(&image->sizeX, 4, 1, file)) != 1)
    {
        printf("Error reading width from %s.\n", filename);
        return 0;
    }
    printf("Width of %s: %lu\n", filename, image->sizeX);

    if((i = fread(&image->sizeY, 4, 1, file)) != 1)
    {
        printf("Error reading height from %s.\n", filename);
        return 0;
    }
    printf("Height of %s: %lu\n", filename, image->sizeY);

    size = image->sizeX * image->sizeY * 3;

    if ((i = fread(&planes, 2, 1, file)) != 1)
    {
        printf("Planes from %s is not 1: %u\n", filename, planes);
        return 0;
    }

    if ((i = fread(&bpp, 2, 1, file)) != 1)
    {
        printf("Error reading bpp from %s.\n", filename);
        return 0;
    }

    if(bpp != 24)
    {
        printf("Bpp from %s is not 24: %u\n", filename, bpp);
        return 0;
    }

    fseek(file, 24, SEEK_CUR);

    image->data = (char*)malloc(size);
    if(image->data == NULL)
    {
        printf("Error allocation memory for color-corrected image data");
        return 0;
    }

    if((i = fread(image->data, size, 1, file)) != 1)
    {
        printf("Error readint image data from %s.\n", filename);
        return 0;
    }

    for (i = 0; i < size; i += 3)
    {
        temp = image->data[i];
        image->data[i] = image->data[i+2];
        image->data[i+2] = temp;
    }

    return 1;
}


void LoadGLTextures()
{
    Image* image1;

    image1 = (Image*) malloc(sizeof(Image));
    if(image1 == NULL)
    {
        printf("Error allocating space for image.");
        exit(0);
    }

    if (!ImageLoad("Data/lesson9/Star.bmp", image1))
    {
        exit(1);
    }

    glGenTextures(1, &texture[0]);

    glBindTexture(GL_TEXTURE_2D, texture[0]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data);

    if (image1 != NULL)
    {
        if(image1->data)
        {
            free(image1->data);
        }
        free(image1);
    }
}


/* A general OpenGL initialization function.  Sets all of the initial parameters. */
void InitGL(int Width, int Height)	        // We call this right after our OpenGL window is created.
{
    LoadGLTextures();
    glEnable(GL_TEXTURE_2D);
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);		// This Will Clear The Background Color To Black
    glClearDepth(1.0);				// Enables Clearing Of The Depth Buffer
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glShadeModel(GL_SMOOTH);			// Enables Smooth Color Shading

    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    glEnable(GL_BLEND);

    for (loop = 0; loop < num; ++loop)
    {
        star[loop].angle = 0.0f;
        star[loop].dist = ((GLfloat)(loop)/num) * 5.0f;
        star[loop].r = rand() % 255;
        star[loop].g = rand() % 255;
        star[loop].b = rand() % 255;
    }

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();				// Reset The Projection Matrix

    gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);	// Calculate The Aspect Ratio Of The Window

    glMatrixMode(GL_MODELVIEW);
}

/* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */
void ReSizeGLScene(int Width, int Height)
{
    if (Height==0)				// Prevent A Divide By Zero If The Window Is Too Small
        Height=1;

    glViewport(0, 0, Width, Height);		// Reset The Current Viewport And Perspective Transformation

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);
    glMatrixMode(GL_MODELVIEW);
}

/* The main drawing function. */
void DrawGLScene()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear The Screen And The Depth Buffer

    glBindTexture(GL_TEXTURE_2D, texture[0]);

    for (loop = 0; loop < num; ++loop)
    {
        glLoadIdentity();				// Reset The View
        glTranslatef(0.0f, 0.0f, zoom);
        glRotatef(tilt, 1.0f, 0.0f, 0.0f);
        glRotatef(star[loop].angle, 0.0f, 1.0f, 0.0f);
        glTranslatef(star[loop].dist, 0.0f, 0.0f);

        glRotatef(-star[loop].angle, 0.0f, 1.0f, 0.0f);
        glRotatef(-tilt, 1.0f, 0.0f, 0.0f);

        if (twinkle)
        {
            glColor4ub(star[num-loop-1].r, star[num-loop-1].g, star[num-loop-1].b, 255);
            glBegin(GL_QUADS);
            glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f);
            glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 0.0f);
            glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, 0.0f);
            glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, 0.0f);
            glEnd();
        }

        glRotatef(spin, 0.0f, 0.0f, 1.0f);
        glColor4ub(star[loop].r, star[loop].g, star[loop].b, 255);
        glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 0.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, 0.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, 0.0f);
        glEnd();

        spin += 0.01f;
        star[loop].angle += ((GLfloat)(loop)) / num;
        star[loop].dist -= 0.01f;

        if (star[loop].dist < 0.0f)
        {
            star[loop].dist += 5.0f;
            star[loop].r = rand() % 255;
            star[loop].g = rand() % 255;
            star[loop].b = rand() % 255;
        }

    }

    // since this is double buffered, swap the buffers to display what just got drawn.
    glutSwapBuffers();
}

/* The function called whenever a key is pressed. */
void keyPressed(unsigned char key, int x, int y) 
{
    /* avoid thrashing this procedure */
    usleep(100);

    switch(key)
    {
        case ESCAPE:  /* If escape is pressed, kill everything. */
            /* shut down our window */
            glutDestroyWindow(window); 

            /* exit the program...normal termination. */
            exit(0);                   
        case 'T':
        case 't':
            printf("T/t pressed; twinkle: %d\n", twinkle);
            twinkle = twinkle? 0: 1;
            break;
    }
}

void specialKeyPressed(int key, int x, int y)
{
    usleep(100);

    switch(key)
    {
        case GLUT_KEY_PAGE_UP:
            zoom -= 0.2f;
            break;
        case GLUT_KEY_PAGE_DOWN:
            zoom += 0.2f;
            break;
        case GLUT_KEY_UP:
            tilt -= 0.5f;
            break;
        case GLUT_KEY_DOWN:
            tilt += 0.5f;
            break;
        case GLUT_KEY_RIGHT:
            zoom += 0.2f;
            break;
        case GLUT_KEY_LEFT:
            zoom -= 0.2f;
            break;
        default:
            break;

    }
}

int main(int argc, char **argv) 
{  
    /* Initialize GLUT state - glut will take any command line arguments that pertain to it or 
       X Windows - look at its documentation at http://reality.sgi.com/mjk/spec3/spec3.html */  
    glutInit(&argc, argv);  

    /* Select type of Display mode:   
       Double buffer 
       RGBA color
       Alpha components supported 
       Depth buffer */  
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);  

    /* get a 640 x 480 window */
    glutInitWindowSize(640, 480);  

    /* the window starts at the upper left corner of the screen */
    glutInitWindowPosition(0, 0);  

    /* Open a window */  
    window = glutCreateWindow("Jeff Molofee's GL Code Tutorial ... NeHe '99");  

    /* Register the function to do all our OpenGL drawing. */
    glutDisplayFunc(&DrawGLScene);  

    /* Go fullscreen.  This is as soon as possible. */
    glutFullScreen();

    /* Even if there are no events, redraw our gl scene. */
    glutIdleFunc(&DrawGLScene);

    /* Register the function called when our window is resized. */
    glutReshapeFunc(&ReSizeGLScene);

    /* Register the function called when the keyboard is pressed. */
    glutKeyboardFunc(&keyPressed);

    glutSpecialFunc(specialKeyPressed);

    /* Initialize our window. */
    InitGL(640, 480);

    /* Start Event Processing Engine */  
    glutMainLoop();  

    return 1;
}

